import java.io.* ;
import java.net.* ;
import java.util.* ;

/** Chatroom server:
 *  Basic functionalities:
 *      - Maintain a list of visitors (name, checked, assocoated OutputStream)
 *          . add visitor (unique) name to list
 *          . remove visitor from list.
 *      - Read a messge of various types from client, and
 *      - Broadcasts message to each visitor.
 *
 * Additional features can be added to Chatroom server:
	List chat rooms/groups (names, topics, nnumber of chaters, starting
   times of groups).
	List names of a chaters in a group/room.
	List names in your group.
	Send private message individual in the same group.
	Ignore loud mouth.
	Start a new group.
	Control the group that you started (band individual).
	Locate an individual in all groups.
	Automatic notification of joining and leaving the group.
	Make chat's client software as applet.

    The server starts a thread to receive one client message. An improved
 architecture can be used in which one thread is started to receive message
 from a group of clients so that less system resources will be consumed.
    To let a single thread to receive messages from a group of users, a
 vector of InputStream and optional ObjectInputStream are passed to the
 thread. The thread loops throught each of inputstream object to see
 whether input is available on the stream. If there is no input, the
 thread tests next stream. If there are input, the thread reads the input
 and pass the message to brocater.

    For a big chatroom, more than one thread can be started to receive
 messages from client. Each thread will handle N clients.

*/
public class Server {
   ServerSocket		svrSocket;
   Broadcaster		broadcaster = new Broadcaster();

   Server() { }

   public static void main( String arg[] ) {
	Server fm =  new Server() ;

	// ----------------------------------------------------------------
	// Create a server socket listenning on port 8900 which is 
	// special port number that allows client and server programs run
	// on the same machine. However, TCP/IP must be installed on that
	// machine even if the client and server are running on the same
	// machine.
	// ----------------------------------------------------------------
	try { fm.svrSocket = new ServerSocket(8989); }
	catch (IOException e) { e.printStackTrace(); }

	/* For debugging only.
	try { System.out.println("Host IP Address: " + 
				InetAddress.getLocalHost().getHostAddress() +
			     " \tHost name: " + InetAddress.getLocalHost().getHostName() );
	} catch (UnknownHostException e) {}
	*/
	while (true) {
	    try {
		Socket chaterSocket = fm.svrSocket.accept() ;
		new Agent(fm.broadcaster, chaterSocket).start();
	    }
	    catch (IOException e) { e.printStackTrace(); }
	}
  }
}

// -------------------------------------------------------------------------
// Agent is a agent who is reseponsible to collect message from 
// chat client and broadcast to other chaters in the group.
// -------------------------------------------------------------------------
class Agent extends Thread {
   Socket			chaterSocket;
   ObjectInputStream		in;
   ObjectOutputStream		out;
   Visitor			visitor = null;
   Broadcaster			bctr;
   Message			message = null ;

   public Agent(Broadcaster b, Socket s) {
	bctr = b; chaterSocket = s; 
	try {
		in  = new ObjectInputStream (chaterSocket.getInputStream());
		out  = new ObjectOutputStream (chaterSocket.getOutputStream());
	        visitor = new Visitor( out );

		bctr.visitors.add(visitor); // Java vector is thread safe.
	} catch (IOException e ) { e.printStackTrace(); }
   }

   public void run() {
	while ( true ) {
	   try {
		// System.out.printf("\nInside Agent run() before readObject()");
		try {
		    message = (Message) in.readObject();
		    ///str = (String) in.readObject();
		} catch (ClassNotFoundException nfd) { nfd.printStackTrace(); }
		if ( message == null ) { chaterSocket.close(); return ; }
		///if ( str == null ) { chaterSocket.close(); return ; }
		
		// System.out.println("\nMessage Rcvd: " + message);

		switch ( message.type ) {
		    case Message.LOGIN :
		      // In case of login and logout, message a visistor's
		      // name. The server will combine "login" and "logout"
		      // with visitor's name to send to every one.
		      visitor.name = new String (message.message) ;
		      Collections.sort(bctr.visitors);
		      message.receivers = bctr.getReceiverNames();
		      // Visitor is added to vector already.
		      bctr.broadcast(message);
		      break;
		
		    // Remove visitor from list. Vector is thread safe.
		    case Message.LOGOUT: 
		    	bctr.visitors.remove( visitor );
		    	try { 
		       		if ( visitor != null && visitor.out != null ) {
				    visitor.out.close(); visitor = null;
				}
		       		if ( chaterSocket != null ) chaterSocket.close();
		        } catch (IOException e) {}
			bctr.broadcast(message);
			break;
		    case Message.PRIVATE:
			// get a list of visitors from its private list, and boradcast.
			System.out.printf("Private Message Received: %s\n", message);
			bctr.broadcastToSelected(message);
			break;
		    case Message.PUBLIC:
			bctr.broadcast(message);
			break;
		}
				// If here is an error in any broadcast, remove visitor.
	   } catch (IOException e) { bctr.visitors.remove(visitor); return ;}
	}
   }
}

class Broadcaster {
   public Vector<Visitor>	visitors = new Vector<Visitor>(10);
   public Broadcaster( ) { }

   public synchronized void broadcast( Message message ) {
	Iterator<Visitor>  itr = visitors.iterator();
	ObjectOutputStream out ;
	if( message == null ) return;
	while ( itr.hasNext() ) {
	   try {
		out = itr.next().out;
		out.writeObject(message);
		out.flush();
	   } catch (IOException e) {}
	}
	System.out.printf("\nMessage-" + message +
		"- broadcasted to %d visitors.", visitors.size());
   }

   public synchronized void broadcastToSelected( Message message ) {
	if( message == null || visitors == null ) return;
	Vector<String> selectedNames = message.getReceiverNames();
	if ( selectedNames == null ) return;
	Iterator<Visitor>  itr = visitors.iterator();
	Iterator<String>   nameItr = selectedNames.iterator();
	Visitor  v = null;
	String   name = null;
	int      k,  nameMatched, count = 0;
	while ( nameItr.hasNext() ) {
	   name = nameItr.next();
	   do {
		if ( ! itr.hasNext() ) return;
		v = itr.next();
		k = name.compareTo( v.name ) ;
	   } while ( k > 0 );
	   if ( k == 0 )
	   try {
		v.out.writeObject(message);
		v.out.flush();
		count++;
	    } catch (IOException e) { }
	 }
         System.out.printf("\n%s -broadcasted to %d visitors.", message, count);
   }

   public String getReceiverNames() {
       StringBuffer names = new StringBuffer( 200 );
       Iterator<Visitor> itr = visitors.iterator();
       while ( itr.hasNext() ) names.append( itr.next().name + Message.NAME_SEPARATOR );
       System.out.printf("visitors after new login: " + names);
       return names.toString().substring(0, names.length() - 1 );
   }
}
